﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Twitter.Tests
{
	public class MemoryBotClient : IBotClient
	{
		/// <summary>
		/// Optional buddy client for operations like "follow" or 
		/// "send message" which affect a "friend".
		/// </summary>
		MemoryBotClient buddy;

		MemoryFriendship friendship;
		MemoryMessages messages;
		MemoryStatuses statuses;
		string screenName;

		public MemoryBotClient(string screenName)
		{
			friendship = new MemoryFriendship(this);
			messages = new MemoryMessages(this);
			statuses = new MemoryStatuses(this);
			this.screenName = screenName;
		}

		public string ScreenName
		{
			get { return screenName; }
		}

		public void SetBuddy(MemoryBotClient buddyClient)
		{
			this.buddy = buddyClient;
		}

		public MemoryFriendship Friendship
		{
			get { return friendship; }
		}

		public MemoryMessages Messages
		{
			get { return messages; }
		}

		public MemoryStatuses Statuses
		{
			get { return statuses; }
		}

		IFriendship IBotClient.Friendship
		{
			get { return Friendship; }
		}

		IMessages IBotClient.Messages
		{
			get { return Messages; }
		}

		IStatuses IBotClient.Statuses
		{
			get { return Statuses; }
		}

		bool IBotClient.Ping()
		{
			return true;
		}

		public User GetProfile(string screenName)
		{
			return new User
			{
				ScreenName = screenName
			};
		}

		public class MemoryFriendship : IFriendship
		{
			HashSet<string> followers = new HashSet<string>();
			HashSet<string> following = new HashSet<string>();
			MemoryBotClient client;

			public MemoryFriendship(MemoryBotClient client)
			{
				this.client = client;
			}

			public void AddFollower(string screenName)
			{
				if (!followers.Contains(screenName))
					followers.Add(screenName);
			}

			public bool Follow(string screenName)
			{
				Guard.ArgumentNotNullOrEmptyString(screenName, "screenName");

				if (client.buddy != null && client.buddy.ScreenName == screenName)
				{
					// Add this client as a follower of the buddy bot.
					client.buddy.friendship.AddFollower(client.ScreenName);
					if (!following.Contains(screenName))
						following.Add(screenName);

					return true;
				}
				else
				{
					return false;
				}
			}

			public IEnumerable<string> GetFollowers()
			{
				return followers;
			}

			public IEnumerable<string> GetFriends()
			{
				return following;
			}

			public void RemoveFollower(string screenName)
			{
				followers.Remove(screenName);
			}

			public bool Leave(string screenName)
			{
				Guard.ArgumentNotNullOrEmptyString(screenName, "screenName");

				if (client.buddy != null && client.buddy.ScreenName == screenName)
				{
					// Remove this client as a follower from the buddy bot.
					client.buddy.friendship.RemoveFollower(client.ScreenName);
					return following.Remove(screenName);
				}
				else
				{
					return false;
				}
			}

			public IEnumerable<string> MakeFriends()
			{
				var notFriends = GetFollowers().Except(GetFriends());

				foreach (string notFriend in notFriends)
				{
					bool success = Follow(notFriend);
					if (success)
					{
						yield return notFriend;
					}
				}
			}

			/// <summary>
			/// Stops following former friends who are not following us in turn.
			/// </summary>
			/// <returns>List of lost friends.</returns>
			public IEnumerable<string> ForgetLostFriends()
			{
				var lostFriends = GetFriends().Except(GetFollowers()).ToList();

				foreach (string lostFriend in lostFriends)
				{
					bool success = Leave(lostFriend);
					if (success)
					{
						yield return lostFriend;
					}
				}
			}

			public bool IsMutualFriendOf(string screenName)
			{
				return AreMutualFriends(this.client.screenName, screenName);
			}

			public bool IsFriendOf(string screenName)
			{
				return IsFriendOf(this.client.screenName, screenName);
			}

			public bool IsFriendOf(string screenName, string friendOf)
			{
				if (screenName != this.client.screenName)
					throw new NotSupportedException();

				return GetFriends().Contains(friendOf);
			}

			public bool AreMutualFriends(string firstScreenName, string secondScreenName)
			{
				if (firstScreenName != this.client.screenName)
					throw new NotSupportedException();

				return GetFollowers().Contains(secondScreenName) &&
					GetFriends().Contains(secondScreenName);
			}
		}

		public class MemoryMessages : IMessages
		{
			MemoryBotClient client;
			internal Dictionary<string, Message> messages = new Dictionary<string, Message>();

			public MemoryMessages(MemoryBotClient client)
			{
				this.client = client;
			}

			public bool Delete(string messageId)
			{
				Guard.ArgumentNotNullOrEmptyString(messageId, "messageId");

				messages.Remove(messageId);
				return true;
			}

			public IEnumerable<Message> GetAll()
			{
				var sorted = messages.Values.ToList();
				sorted.Sort(
					(x, y) => x.Created.Value.CompareTo(y.Created.Value));

				return sorted;
			}

			public Message GetLatest()
			{
				var sorted = messages.Values.ToList();
				sorted.Sort(
					(x, y) => x.Created.Value.CompareTo(y.Created.Value));

				return sorted[sorted.Count - 1];
			}

			public void AddMessage(Message message)
			{
				messages.Add(message.Id, message);
			}

			public Message Send(string screenName, string message)
			{
				Guard.ArgumentNotNullOrEmptyString(screenName, "screenName");
				Guard.ArgumentNotNullOrEmptyString(message, "message");

				Message msg = new Message
				{
					Id = Guid.NewGuid().ToString(),
					Created = DateTime.Now,
					Sender = new User
					{
						Id = client.screenName,
						Name = client.screenName,
						ScreenName = client.screenName
					},
					Text = message,
					Recipient = new User
					{
						Id = screenName,
						Name = screenName,
						ScreenName = screenName
					}
				};

				if (client.buddy != null && client.buddy.ScreenName == screenName)
				{
					// Add the message to the buddy bot.
					client.buddy.Messages.AddMessage(msg);
				}

				return msg;
			}
		}

		public class MemoryStatuses : IStatuses
		{
			MemoryBotClient client;
			Dictionary<string, Status> statuses = new Dictionary<string,Status>();

			public MemoryStatuses(MemoryBotClient client)
			{
				this.client = client;
			}

			public bool Delete(string statusId)
			{
				Guard.ArgumentNotNullOrEmptyString(statusId, "statusId");

				statuses.Remove(statusId);
				return true;
			}

			public Status GetLatest()
			{
				return statuses.Values.AsEnumerable().OrderBy(
					x => x.Created).LastOrDefault();
			}

			public bool Update(string newStatus)
			{
				Guard.ArgumentNotNullOrEmptyString(newStatus, "newStatus");

				Status status = new Status
				{
					Id = Guid.NewGuid().ToString(),
					Created = DateTime.Now,
					Text = newStatus,
					User = new User
					{
						Id = client.screenName,
						Name = client.screenName,
						ScreenName = client.screenName
					}
				};

				statuses.Add(status.Id, status);
				return true;
			}
		}
	}
}
